<?php
/**
* SavvyHelper, a group of helpers for symfony framework.
*
* SavvyHelper includes a lot of useful helpers for your almost daily work
* in any web applications. Helpers included:
* <ul>
* 	<li>Simple Dynamic HTML Table</li>
*  <li>Simple Edit Form</li>
* <li>Save Form</li>
*  <li>Simple Delete Form</li>
* </ul>
* @author Ahmed El.Hussaini <satisfaction144@yahoo.com>
* @version 1.0
*/


/**
* A function that generates an HTML dynamic table using an array containing a collection of model objects.
* Consider this, we have a table in a database that consists of the following fields (id, title, name, email, address, phone, login_id).
*
* Examples:
*
* The first paramter <b>$model</b> is an array that contains a collection of model objects.
* The second parameter, if it's null, all fields will be displayed except the primary and foreign keys, if it's an array, it must be a list of getter methods of the fields to be displayed.
*
* The third parameter is an array, first key; <b>"routing-params"</b> specifies the parameters to be passed to the desired routing rules.
*
*For example, say you have a routing rule <b>"@edit"</b> that
* you use to edit records from the current table, and this rule expects 2 parameters to be passed to it, <b>"title"</b> and <b>"name"</b>. So we define the parameters name and value using the
* <b>"routing-params"</b> array
*
* <b>Displaying all fields with manage links</b>
* <code>
* <?php echo getSimpleTable($myModel, null, 
*		array('routing-params'=>array('title'=>'getTitle', 'name'=>'getName'),
*		'@edit'=>'Edit', '@delete'=>'Delete')) ?>
* </code>
*
* <b>Displaying all fields without manage links</b>
* <code>
* <?php echo getSimpleTable($myModel, null, null) ?>
* </code>
*
* <b>Displaying selected fields only</b>
* <code>
* <?php echo getSimpleTable($myModel,
*		array('getTitle', 'getName', 'getEmail'),
*		array('routing-params'=>array('title'=>'getTitle', 'name'=>'getName'),
*		'@edit'=>'Edit', '@delete'=>'Delete')) ?>
* </code>
* @param object|array $model The Model to be displayed
* @param array $displayMethods A list of getter methods. If used, only the fields whose methods were passed will be displayed in the genereated table
* @param array $routingRules A list of routing rules with text labels to be used when displaying links to edit or delete in the generated table
* @return string
*/
function getSimpleTable($model, $displayMethods, $routingRules) {
	$fieldsHelper = new FieldsHelper($model,$displayMethods, $routingRules);
	$fieldsInfo = $fieldsHelper->getFieldsInfo();
	if(!is_array($displayMethods)) {
		$displayMethods = $fieldsHelper->getMethods();
	}
	$table = "<table border=\"1\" cellpadding=\"3\">";
	$table .= "<tr>\n";
	for($i=0; $i<count($displayMethods); $i++) {
		//Don't display any primary or foreign keys
		foreach($fieldsInfo as $fieldInfo) {
			if($fieldInfo['method-name'] === $displayMethods[$i]) {
				if($fieldInfo['element-name'] != 'id' && strripos($fieldInfo['element-name'], '_id') != strlen($fieldInfo['element-name'])-3) {
					$table .= "<th>".$fieldInfo['label']."</th>\n";
				}
			}
		}	
	}
	if(is_array($routingRules) && count($routingRules) > 0) {
		$table .= "<th>Manage</th>\n";
	}
	$table .= "</tr>\n";
	foreach($model as $record) {
		$table .= "<tr>\n";
		for($i=0; $i<count($displayMethods); $i++) {
			foreach($fieldsInfo as $fieldInfo) {
				if($fieldInfo['method-name'] === $displayMethods[$i]) {
					if($fieldInfo['element-name'] != 'id' && strripos($fieldInfo['element-name'], '_id') != strlen($fieldInfo['element-name'])-3) {
						$table .= "<td>".$record->$fieldInfo['method-name']()."</td>\n";
					}
				}
			}
		}
			if(is_array($routingRules) && count($routingRules) > 0) {
				$table .= "<td>";
				$routing = array_keys($routingRules);
				for($j=1; $j<count($routing); $j++) {
					$routingParams = "?";
					$k = 0;
					while(list($routeParam, $method_name) = each($routingRules['routing-params'])) {
						$routingParams .= "$routeParam=".$record->$method_name();
						if($k < count($routingRules['routing-params'])-1) {
							$routingParams .= '&';
						}
						$k++;
					}
					reset($routingRules['routing-params']);
					$table .= link_to($routingRules[$routing[$j]], $routing[$j].$routingParams);
					if($j<count($routing)-1) {
						$table .=" | ";
					}
				}
				$table .= "</td>\n";
			}
		$table .= "</tr>\n";
	}
	$table .= "</table>\n";
	return $table;
}

/**
* A function that generate an HTML dynamic edit form for a model object.
* Examples:
*Displays a form that contains all fields except primary and foreign keys. Only the "Title" and "Name" are editable.
* <code>
* <?php echo getEditForm($project, array('getTitle', 'getName'), null) ?>
* </code>
* @param object $model The model to be updated
* @param array $editableMethods The fields to be edited, by default all fields are disabled
* @param array $attributes HTML attributes
*/
function getEditForm($model, $editableMethods, $attributes) {
	$fieldsHelper = new FieldsHelper($model);
	$fieldsInfo = $fieldsHelper->getFieldsInfo();
	$methods = $fieldsHelper->getMethods();
	$peer = $fieldsHelper->getPeer();
	$tableMap = call_user_func(array($peer, 'getTableMap'));
	$formContent = "";
	if(isset($attributes['action'])) {
		$action = $attributes['action'];
	} else {
		$action = sfRouting::getInstance()->getCurrentInternalUri();
	}
	$formContent .= form_tag($action)."\n";
	foreach($fieldsInfo as $fieldInfo) {
		$name = $fieldInfo['element-name'];
		$value = $model->$fieldInfo['method-name']();
		$label =  $fieldInfo['label'];
		$type = $tableMap->getColumn($name)->getType();
		$editable = null;
		if($fieldInfo['element-name'] != 'id' && strripos($fieldInfo['element-name'], '_id') != strlen($fieldInfo['element-name'])-3) {
			if(is_array($editableMethods) && array_search($fieldInfo['method-name'], $editableMethods) !== false) {
				$editable = true;
			} else {
				$editable = false;
			}	
		} else {
			$type = 'hidden';
		}
		$formContent .= FormElements::getFormElement($name, $value, $label, $type, $editable, $attributes);
	}
	$formContent .= "<br />".submit_tag('Update')."\n";
	$formContent .= "</form>";
	return $formContent;
}

/**
* A function that saves a form data to a model object and then to the database, after saving the data
* a message is added to the request, so you can display it.
*
* <code>
* <?php saveFormData($model, $sf_request, 'Item Deleted') ?>
* </code>
* The 'Item Deleted' can be displayed anywhere you want, it's up to you really
* @param object $model The model object that will be updated with the form data
* @param sfRequest $request A reference to the current request object
* @param string $msg The message to be displayed
*/
function saveFormData($model, sfRequest $request, $msg) {
	$model->fromArray($request->getParameterHolder()->getAll(), BasePeer::TYPE_FIELDNAME);
	$model->save();
	$request->setAttribute("msg", $msg);
}

/**
* A function the displays a vertical table containing the model to be deleted.
* @param object $model The model object to be deleted
* @param string $confirmMsg The confirmation message displayed right before deleting the object
* @param array $attributes An array containing html attributes, currently use only the action attribute
*/
function getDeleteTable($model, $confirmMsg, $attributes) {
	$fieldsHelper = new FieldsHelper($model);
	$fieldsInfo = $fieldsHelper->getFieldsInfo();
		if(isset($attributes['action'])) {
		$action = $attributes['action'];
	} else {
		$action = sfRouting::getInstance()->getCurrentInternalUri();
	}
	$html = form_tag($action);
	$html .= "<table border=\"1\">\n";
	foreach($fieldsInfo as $fieldInfo) {
		$html .= "<tr>\n";
		$html .= "<th>".$fieldInfo['label']."</th>\n";
		$html .= "<td>".$model->$fieldInfo['method-name']()."</td>\n";
		$html .= "</tr>\n";
	}
	$html .= "</table>\n";
	$html .= "<input type=\"button\" value=\"Delete\" onClick=\"if(confirm('$confirmMsg')) document.forms[0].submit();\" />\n";
	$html .= "</form>\n";
	return $html;
}

class FieldsHelper {
	private $fieldsInfo;
	
	private $fieldNames;
	
	private $methods;

	private $labels;
	
	private $model;
	
	private $peer;
	
	private $routingRules;
	
	private $displayMethods;
	
	public function FieldsHelper($model) {
		$this->object = $model;
		$this->init();
	}
	
	/**
	 * Initilaize all properties
	 */
	private function init() {
		//init peer
		$modelType = gettype($this->object);
		if($modelType == 'array') {
			$this->peer = $this->object[0]->getPeer();
		} else {
			$this->peer = $this->object->getPeer();
		}
		
		//init fieldNames
		$this->fieldNames = call_user_func(array($this->peer, 'getFieldNames'), BasePeer::TYPE_FIELDNAME);
		
		//init lables, methods, and display
		$this->methods = array();
		$this->formattedNames = array();
		$this->labels = array();
		foreach ($this->fieldNames as $fieldName) {
			$label = "";
			$methodName = "";
			if(strpos($fieldName,"_") > 0) {
				$fieldNamesSplitted = preg_split("/_/", $fieldName);
				$count = count($fieldNamesSplitted);
				for($i=0; $i<$count; $i++) {
					if($i < $count) {
						$methodName .= ucfirst($fieldNamesSplitted[$i]);
						$label .= ucfirst($fieldNamesSplitted[$i])." ";
					} else {
						$methodName .= ucfirst($fieldNamesSplitted[$i]);
						$label .= ucfirst($fieldNamesSplitted[$i]);
					}
				}
				$this->labels[] = $label;
				$methodName = 'get'.$methodName;
				$this->methods[] = $methodName;
			} else {
				$label = ucfirst($fieldName);
				$this->labels[] = $label;
				$methodName = 'get'.ucfirst($fieldName);
				$this->methods[] = $methodName;
			}
			$fieldMeta = array('element-name' => $fieldName, 'method-name' => $methodName, 'label' => $label);
			$this->fieldsInfo[] = $fieldMeta;
		}
	}
	
	/**
	*	Returns an array that contains info about each field
	*/
	public function getFieldsInfo() {
		return $this->fieldsInfo;
	}
	
	/**
	 * Returns an array that contains all the fields names as in the db
	 */
	public function getFieldNames() {
		return $this->fieldNames;
	}
	
	/**
	 * Returns an array that contains all the getters of all the fields
	 */
	public function getMethods() {
		return $this->methods;
	}
	
	/**
	 * Returns an array that contains all labels of all fields
	 */
	public function getLabels() {
		return $this->labels;
	}
	
	public function getPeer() {
		return $this->peer;
	}
}

class FormElements {
	public static function getFormElement($name, $value, $label, $type, $editable, $attributes) {
		$formElement = '';
		switch($type) {
			case 'string':
			case 'int':
			case 'double':
				$formElement = self::getInputText($name, $value, $label, $editable, $attributes);
				break;
			case 'hidden':
				$formElement = self::getInputHidden($name, $value);
				break;
		}
		return $formElement;
	}
	
	/**
	*	Returns a text field input
	*/
	private static function getInputText($name, $value, $label, $editable, $attributes) {
		$inputText = "<b><label for=\"$name\">$label</label></b><br />\n";
		$inputText .= "<input type=\"text\" name=\"$name\" value=\"$value\" ";
		if(!$editable) {
			$inputText .= "disabled=\"disabled\"";
		}
		if(count($attributes) > 0) {
			foreach($attributes as $key=>$attrValue) {
				$inputText .= "$key=\"$attrValue\" ";
			}
		}
		$inputText .= "/><br /><br />\n";
		return $inputText;
	}
	
	/**
	*	Returns a hidden field
	*/
	private static function getInputHidden($name, $value) {
		$inputHidden = "<input type=\"hidden\" name=\"$name\" value=\"$value\" />\n";
		return $inputHidden;
	}
}